﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using SimpleIR;

namespace PInfoModel
{
	/// <summary>
	/// Complete analysis information for a single call context of a method.
	/// </summary>
	public sealed class CompleteAnalysisInfoMethodContext
	{
		/// <summary>
		/// A unique identifier for this method context (is the same as the ID for the MethodContextState that this is associated with).
		/// </summary>
		private readonly int m_contextid;

		/// <summary>
		/// The exact type of the reciver object (for virtual calls or the defining class for statics).
		/// </summary>
		private readonly IRType m_contexttoken;

		/// <summary>
		/// If the context is tracking calls within a recursive call cycle.
		/// </summary>
		private readonly bool m_isforrecursivecall;

		/// <summary>
		/// The invoke identity of this context state.
		/// </summary>
		private readonly IRInvokeIdentity m_invokeid;

		/// <summary>
		/// The body definition that this is operating on.
		/// </summary>
		private readonly IRBodyDef m_bodydef;

		/// <summary>
		/// The input and output states for this particular analysis context.
		/// </summary>
		private readonly PIModel m_instate;
		private PIModel m_outstate;

		/// <summary>
		/// Number of times we have re-analyzed the body.
		/// </summary>
		private readonly int m_numberOfAnalysisPasses;

		/// <summary>
		/// Map from blockids in the method body to the state at entry to the block.
		/// </summary>
		private readonly Dictionary<long, PIModel> m_blockidtomodelmap;

		/// <summary>
		/// A set of tuples (caller invokeid, the caller context, and the line number) that are associated with this context.
		/// </summary>
		private readonly HashSet<Tuple<IRInvokeIdentity, int, int>> m_callerscontextsandlines;

		/// <summary>
		/// Map from each call to maps that is a list of all targetmethods, the id of the context state that is used and maps that 
		/// relate ids/externalrefs in the caller state to the related info in the callee.
		/// </summary>
		private readonly Dictionary<int, HashSet<CallerCalleeIDMapping>> m_calltocalleestatemaps;

		/// <summary>
		/// Map from the escids that appear in this to the fresh escape ids given for the return heap of this
		/// </summary>
		public Dictionary<PIEscapeSite, HashSet<PIEscapeSite>> CalleeEscToCallerEscMap;

		/// <summary>
		/// Map from program locations to the program point info for the location.
		/// dictionary from usemod lines to ProgramPointInfo
		/// </summary>
		private WeakReference m_programPointToPointInfoMap;

		/// <summary>
		/// The location the program point info is serialized into.
		/// </summary>
		private string m_serializationloc;

		/// <summary>
		/// Deserializer for the program point info.
		/// </summary>
		private PInfoDeSerializer m_pinds;

		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_contexttoken != null);
			Contract.Invariant(this.m_invokeid != null);
			Contract.Invariant(this.m_callerscontextsandlines != null);
			Contract.Invariant(this.m_blockidtomodelmap != null);
			Contract.Invariant(this.m_calltocalleestatemaps != null);
		}

		/// <summary>
		/// Make a CallStateMapEntry for normal project/extend memo analysis.
		/// </summary>
		public CompleteAnalysisInfoMethodContext(int contextid, IRInvokeIdentity invid, IRBodyDef bdf, PIModel inarg, bool isinsiderecursive, IRType ctxttoken, int numiters)
		{
			this.m_contextid = contextid;
			this.m_bodydef = bdf;
			this.m_contexttoken = ctxttoken;
			this.m_callerscontextsandlines = new HashSet<Tuple<IRInvokeIdentity, int, int>>();
			this.m_invokeid = invid;
			this.m_isforrecursivecall = isinsiderecursive;
			this.m_numberOfAnalysisPasses = numiters;
			
			this.m_instate = inarg;
			this.m_outstate = null;
			
			this.m_blockidtomodelmap = new Dictionary<long, PIModel>();
			this.m_calltocalleestatemaps = new Dictionary<int, HashSet<CallerCalleeIDMapping>>();

			this.m_programPointToPointInfoMap = null;
			this.m_serializationloc = null;
			this.m_pinds = null;
		}

		public void FinalizeInputAndOutputStates(PIModel outarg)
		{
			this.m_outstate = outarg;
		}

		////////////////////////////////////
		//Code that should be called in sequence during call analysis to construct relations between caller scope identities, escape info, and cutpoint sources 
		//and the callee scope identities and cutpoints. Also we want to map from callee escapes to caller escapes.

		CallerCalleeIDMapping mt_currentcimm;

		/// <summary>
		/// Call this first to initialize the temp info for analyzing how things get remapped at a call.
		/// </summary>
		public void InitializeCallInfoProcessAtLine(int callerline, IRInvokeIdentity callerinv, int callerid, IRInvokeIdentity calleeinv)
		{
			CallerCalleeIDMapping cimm = new CallerCalleeIDMapping(callerline, callerinv, callerid, calleeinv);

			if(!this.m_calltocalleestatemaps.ContainsKey(callerline))
				this.m_calltocalleestatemaps.Add(callerline, new HashSet<CallerCalleeIDMapping>());
			this.m_calltocalleestatemaps[callerline].Add(cimm);

			this.mt_currentcimm = cimm;
		}

		/// <summary>
		/// Call this for to set the initial mapping from caller id/escape to callee ids.
		/// </summary>
		public void SetInitialInfoForMappingIdentitiesIntoCalleeScope(int newid, int[] fromids, Tuple<PIEscapeSite, IRRootIdentifier>[] fromescs)
		{
			MemLocID extid = MemLocID.MakeExternalMemLocation(newid);
			for(int i = 0; i < fromids.Length; ++i)
			{
				MemLocID ml = MemLocID.MakeExternalMemLocation(fromids[i]);
				if(!this.mt_currentcimm.CallerIDToCalleeIDMap.ContainsKey(ml))
					this.mt_currentcimm.CallerIDToCalleeIDMap.Add(ml, new HashSet<MemLocID>());
				this.mt_currentcimm.CallerIDToCalleeIDMap[ml].Add(extid);
			}

			for(int i = 0; i < fromescs.Length; ++i)
			{
				MemLocID ml = MemLocID.MakeInternalMemLocation(fromescs[i].Item1.EscIdentity, fromescs[i].Item1.EscLine, fromescs[i].Item2);
				if(!this.mt_currentcimm.CallerIDToCalleeIDMap.ContainsKey(ml))
					this.mt_currentcimm.CallerIDToCalleeIDMap.Add(ml, new HashSet<MemLocID>());
				this.mt_currentcimm.CallerIDToCalleeIDMap[ml].Add(extid);
			}
		}

		/// <summary>
		/// Update the caller information for this call context.
		/// </summary>
		public void UpdateCallersForCallee(CompleteAnalysisInfoMethodContext callerinfo, Dictionary<int, int> idremap)
		{
			//set the callee context id for the current call info
			callerinfo.mt_currentcimm.CalleeContextID = this.ContextID;

			Dictionary<MemLocID, HashSet<MemLocID>> ffmap = new Dictionary<MemLocID, HashSet<MemLocID>>();
			foreach(KeyValuePair<MemLocID, HashSet<MemLocID>> kvphs in callerinfo.mt_currentcimm.CallerIDToCalleeIDMap)
			{
				ffmap.Add(kvphs.Key, new HashSet<MemLocID>());

				foreach(MemLocID mml in kvphs.Value)
				{
					if(mml.IsInternalID)
						ffmap[kvphs.Key].Add(mml);
					else
					{
						MemLocID rml = MemLocID.MakeExternalMemLocation(idremap[mml.ExternalIDValue]);
						ffmap[kvphs.Key].Add(rml);
					}
				}
			}
			callerinfo.mt_currentcimm.CallerIDToCalleeIDMap = ffmap;

			//add the caller info as a caller to the callee
			this.m_callerscontextsandlines.Add(new Tuple<IRInvokeIdentity, int, int>(callerinfo.m_invokeid, callerinfo.m_contextid, callerinfo.mt_currentcimm.CallerLine));
		}

		/// <summary>
		/// Compute the mappings from the callee scope escapes to the new caller scope escapes.
		/// </summary>
		public void SetInfoForMappingCalleeEscapeToCallerScope(Dictionary<PInfoModel.PIEscapeSite, HashSet<PInfoModel.PIEscapeSite>> calleetocallerescmap)
		{
			this.CalleeEscToCallerEscMap = calleetocallerescmap;
		}

		/// <summary>
		/// Compute the final mappings for information flow from the caller to/from the callee.
		/// </summary>
		public void FinalizeCallerCalleeMappings()
		{
			this.mt_currentcimm = null;
		}

		////////////////////////////////////
		//Accessors for the various parts of the complete contexts.

		/// <summary>
		/// A unique identifier for this method context.
		/// </summary>
		public int ContextID
		{ get { return this.m_contextid; } }

		/// <summary>
		/// The exact type of the reciver object (for virtual calls or the defining class for statics).
		/// </summary>
		public IRType ContextToken
		{ get { return this.m_contexttoken; } }

		/// <summary>
		/// If the context is tracking calls within a recursive call cycle.
		/// </summary>
		public bool IsForRecursiveCall
		{ get { return this.m_isforrecursivecall; } }

		/// <summary>
		/// The invoke identity of this context state.
		/// </summary>
		public IRInvokeIdentity InvokeID
		{ get { return this.m_invokeid; } }

		/// <summary>
		/// A set of tuples (caller invokeid, the caller context, and the line number) that are associated with this context.
		/// </summary>
		public HashSet<Tuple<IRInvokeIdentity, int, int>> CallersContextsAndLines
		{ get { return this.m_callerscontextsandlines; } }

		/// <summary>
		/// A map from lines with calls to the set of possible targets and the memory location mappings.
		/// </summary>
		public Dictionary<int, HashSet<CallerCalleeIDMapping>> CallToCalleeStateMaps
		{ get { return this.m_calltocalleestatemaps; } }

		/// <summary>
		/// The input state for this particular analysis context.
		/// </summary>
		public PIModel InState
		{ get { return this.m_instate; } }

		/// <summary>
		/// The output state for this particular analysis context.
		/// </summary>
		public PIModel OutState
		{ get { return this.m_outstate; } }

		/// <summary>
		/// Number of times we have re-analyzed the body.
		/// </summary>
		public int NumberOfAnalysisPasses
		{ get { return this.m_numberOfAnalysisPasses; } }

		/// <summary>
		/// Map from block ids in the method body to the state at entry to the block.
		/// </summary>
		public Dictionary<long, PIModel> BlockIDToStateMap
		{ get { return this.m_blockidtomodelmap; } }

		////////////////////////////////////
		//Support for on demand serialization/deserialization of the complete context info

		/// <summary>
		/// A table to memoize the program point info sets, and then allow us to serialize them as memory presure increases
		/// Tuples are (ID, Age, InfoMap)
		/// </summary>
		private static List<Tuple<CompleteAnalysisInfoMethodContext, Dictionary<int, ProgramPointInfo>>> s_CompleteProgPointInfoMemoTable;

		private static Random s_remrnd;

		public static void InitializeProgPointInfoTables()
		{
			s_CompleteProgPointInfoMemoTable = new List<Tuple<CompleteAnalysisInfoMethodContext, Dictionary<int, ProgramPointInfo>>>();
			s_remrnd = new Random(1);
		}

		public static void CleanUpProgPointInfoTables()
		{
			s_CompleteProgPointInfoMemoTable = null;
			s_remrnd = null;
		}

		private void RegisterPPI(Dictionary<int, ProgramPointInfo> ppi)
		{
			if(s_CompleteProgPointInfoMemoTable.Count > 150)
			{
				int rstride = (s_remrnd.Next(3) + 2);
				for(int i = 0; i < s_CompleteProgPointInfoMemoTable.Count; ++i)
				{
					if(i % rstride == 0)
					{
						s_CompleteProgPointInfoMemoTable[i].Item1.SerializeProgramPointInfo(s_CompleteProgPointInfoMemoTable[i].Item2);
						s_CompleteProgPointInfoMemoTable[i] = null;
					}

					s_CompleteProgPointInfoMemoTable.RemoveAll((Tuple<CompleteAnalysisInfoMethodContext, Dictionary<int, ProgramPointInfo>> mv) => mv == null);
				}
			}

			s_CompleteProgPointInfoMemoTable.Add(new Tuple<CompleteAnalysisInfoMethodContext, Dictionary<int, ProgramPointInfo>>(this, ppi));
		}

		private Dictionary<int, ProgramPointInfo> ResolvePPI()
		{
			Tuple<CompleteAnalysisInfoMethodContext, Dictionary<int, ProgramPointInfo>> fres = s_CompleteProgPointInfoMemoTable.Find((Tuple<CompleteAnalysisInfoMethodContext, Dictionary<int, ProgramPointInfo>> mv) => mv.Item1 == this);
			if(fres != null)
				return fres.Item2;
			else
			{
				Dictionary<int, ProgramPointInfo> tres = this.DeserializeProgramPointInfo();
				this.RegisterPPI(tres);
				return tres;
			}
		}

		private void PPILSerialize(PInfoSerializer pins, Dictionary<int, ProgramPointInfo> ppi)
		{
			pins.WriteStartTag("ProgPointInfoDict");
			pins.WriteCollectionStartTag("PPIL", ppi.Count);
			foreach(KeyValuePair<int, ProgramPointInfo> ppip in ppi)
			{
				pins.WriteStartTag("PPI");

				pins.WriteInt("Line", ppip.Key);
				pins.WriteBool("UReach", ppip.Value == null);
				if(ppip.Value != null)
					ppip.Value.Serialize(pins);

				pins.WriteEndTag("PPI");
			}
			pins.WriteCollectionEndTag("PPIL");
			pins.WriteEndTag("ProgPointInfoDict");
		}

		private void PPILDeserialize(Dictionary<int, ProgramPointInfo> ppid)
		{
			int ct = this.m_pinds.ReadCollectionStartTag("PPIL");
			for(int i = 0; i < ct; ++i)
			{
				this.m_pinds.ReadStartTag("PPI");

				int line = this.m_pinds.ReadInt("Line");
				bool ureach = this.m_pinds.ReadBool("UReach");

				ProgramPointInfo ppi = null;
				if(!ureach)
					ppi = ProgramPointInfo.Deserialize(this.m_pinds);

				ppid.Add(line, ppi);
				this.m_pinds.ReadEndTag("PPI");
			}
			this.m_pinds.ReadCollectionEndTag("PPIL");
		}

		private void SerializeProgramPointInfo(Dictionary<int, ProgramPointInfo> ppi)
		{
			if(System.IO.File.Exists(this.m_serializationloc))
				return;

			using(System.IO.FileStream fs = new System.IO.FileStream(this.m_serializationloc, System.IO.FileMode.Create))
			{
				using(System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fs))
				{
					PInfoSerializer pins = new PInfoSerializerBinary(bw);
					this.PPILSerialize(pins, ppi);
					this.m_pinds = new PInfoDeSerializerBinary(pins.TypeMap, pins.FieldMap, pins.PathMap, pins.RootMap, pins.StringMap);
				}
			}

			/*
			System.Xml.XmlWriterSettings xws = new System.Xml.XmlWriterSettings() { CloseOutput = true, Indent = true };

			using(System.IO.FileStream fs = new System.IO.FileStream(this.m_serializationloc, System.IO.FileMode.Create))
			{
				using(System.IO.Compression.DeflateStream dfs = new System.IO.Compression.DeflateStream(fs, System.IO.Compression.CompressionMode.Compress))
				{
					using(System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(dfs, xws))
					{
						PInfoSerializer pins = new PInfoSerializerXML(xw);
						this.PPILSerialize(pins, ppi);
						this.m_pinds = new PInfoDeSerializerXML(pins.TypeMap, pins.FieldMap, pins.PathMap, pins.RootMap, pins.StringMap);
					}
				}
			}
			*/ 
		}

		private Dictionary<int, ProgramPointInfo> DeserializeProgramPointInfo()
		{
			Dictionary<int, ProgramPointInfo> ppid = new Dictionary<int, ProgramPointInfo>();

			using(System.IO.FileStream fs = new System.IO.FileStream(this.m_serializationloc, System.IO.FileMode.Open))
			{
				using(System.IO.BinaryReader br = new System.IO.BinaryReader(fs))
				{
					br.ReadInt32();
					this.m_pinds.SetDeserializationStream(br);
					this.PPILDeserialize(ppid);
				}
			}

			/*
			System.Xml.XmlReaderSettings xrs = new System.Xml.XmlReaderSettings() { CloseInput = true, IgnoreWhitespace = true };

			using(System.IO.FileStream fs = new System.IO.FileStream(this.m_serializationloc, System.IO.FileMode.Open))
			{
				using(System.IO.Compression.DeflateStream dfs = new System.IO.Compression.DeflateStream(fs, System.IO.Compression.CompressionMode.Decompress))
				{
					using(System.Xml.XmlReader xr = System.Xml.XmlReader.Create(dfs, xrs))
					{
						xr.ReadStartElement();
						this.m_pinds.SetDeserializationStream(xr);
						this.PPILDeserialize(ppid);
					}
				}
			}
			*/ 

			foreach(IRBasicBlock bb in this.m_bodydef.Body.BasicBlockList)
			{
				if(!this.m_blockidtomodelmap.ContainsKey(bb.BlockID))
					continue;

				PIModel prem = this.m_blockidtomodelmap[bb.BlockID];
				foreach(IRByteCode bc in bb.Operations)
				{
					if(!ppid.ContainsKey(bc.UseModLoc))
						continue;

					ProgramPointInfo ppold = ppid[bc.UseModLoc];
					ProgramPointInfo ppnew = new ProgramPointInfo(prem, ppold.PostOperationModel);
					ppid[bc.UseModLoc] = ppnew;

					prem = ppnew.PostOperationModel;
				}
			}

			this.m_pinds.SetDeserializationStream(null);

			return ppid;
		}

		public void CommitCompleteProgramPointInfo(string sloc, Dictionary<int, ProgramPointInfo> ppi)
		{
			this.RegisterPPI(ppi);

			this.m_serializationloc = sloc;
			this.m_programPointToPointInfoMap = new WeakReference(ppi);
		}

		/// <summary>
		/// Map from program locations to the program point info for the location.
		/// </summary>
		public Dictionary<int, ProgramPointInfo> ProgramPointToPointInfoMap()
		{
			Dictionary<int, ProgramPointInfo> ppid = (Dictionary<int, ProgramPointInfo>)this.m_programPointToPointInfoMap.Target;
			
			if(ppid == null)
			{
				ppid = this.ResolvePPI();

				this.m_programPointToPointInfoMap = new WeakReference(ppid);
			}

			return ppid;
		}
	}
}
